WebGL JavaScript

Draw a Plane Cut by a Semitransparent Cube

This JavaScript program demonstrates how to draw a plane cut by a cube with transparency in a WebGL program.

DrawAPlaneCutByACube.html

<!DOCTYPE html>
<html>
  <head>
    <title>XoaX.net's WebGL</title>
    <script  id="idVertexShader" type="c">
      attribute vec4 av4Vertex;
      attribute vec4 av4Color;
      varying vec4 vv4Color;
      void main() {
        gl_Position = av4Vertex;
        gl_PointSize = 5.0;
        vv4Color = av4Color;
      }
    </script>

    <script  id="idFragmantShader" type="c">
      precision mediump float;
      varying vec4 vv4Color;
      void main() {
        gl_FragColor = vv4Color;
      }
    </script>

    <script type="text/javascript">
    var gqGL = null;
    var gqProgram = null;
    function CreateProgramAndContext() {
      // Get the WebGL Context
      var qCanvas = document.querySelector("#idCanvas");
      gqGL = qCanvas.getContext("webgl");

      // Compile the vertex shader
      var sVertexShaderCode = document.querySelector("#idVertexShader").text;
      var qVertexShader = gqGL.createShader(gqGL.VERTEX_SHADER);
      gqGL.shaderSource(qVertexShader, sVertexShaderCode);
      gqGL.compileShader(qVertexShader);

      // Compile the fragment shader
      var sFragmentShaderCode = document.querySelector("#idFragmantShader").text;
      var qFragmentShader = gqGL.createShader(gqGL.FRAGMENT_SHADER);
      gqGL.shaderSource(qFragmentShader, sFragmentShaderCode);
      gqGL.compileShader(qFragmentShader);

      // Compile and link the program
      gqProgram = gqGL.createProgram();
      gqGL.attachShader(gqProgram, qVertexShader);
      gqGL.attachShader(gqProgram, qFragmentShader);
      gqGL.linkProgram(gqProgram);
      gqGL.useProgram(gqProgram);
    }

    var gfaTransformedVertices = null;
    var gfaVertexColors = null;
	function CreateBuffers() {
      var qVerticesBuffer = gqGL.createBuffer();
      gqGL.bindBuffer(gqGL.ARRAY_BUFFER, qVerticesBuffer);
      gqGL.bufferData(gqGL.ARRAY_BUFFER, gfaTransformedVertices, gqGL.STATIC_DRAW);

      var qVertexLoc = gqGL.getAttribLocation(gqProgram, 'av4Vertex');
      gqGL.vertexAttribPointer(qVertexLoc, 4, gqGL.FLOAT, false, 0, 0);
      gqGL.enableVertexAttribArray(qVertexLoc);

      var qColorsBuffer = gqGL.createBuffer();
	  gqGL.bindBuffer(gqGL.ARRAY_BUFFER, qColorsBuffer);
	  gqGL.bufferData(gqGL.ARRAY_BUFFER, gfaVertexColors, gqGL.STATIC_DRAW);

	  var qColors = gqGL.getAttribLocation(gqProgram, 'av4Color');
	  gqGL.vertexAttribPointer(qColors, 4, gqGL.FLOAT, false, 0, 0);
      gqGL.enableVertexAttribArray(qColors);
	}

    var gfaVertices = null;
    var giAddedPoints = 0;
    function Initialization() {
      gfaVertices = new Float32Array([
        // These must be drawn back to front to render it correctly with the alpha blending
        -1.0, 1.0, 1.0, 1.0,  -1.0, 1.0, -1.0, 1.0,   -1.0, -1.0, 1.0, 1.0,   -1.0, -1.0, -1.0, 1.0,  // x = -1
        1.0, -1.0, 1.0, 1.0,  1.0, -1.0, -1.0, 1.0,   -1.0, -1.0, 1.0, 1.0,   -1.0, -1.0, -1.0, 1.0,  // y = -1
        1.0, 1.0, -1.0, 1.0,  1.0, -1.0, -1.0, 1.0,   -1.0, 1.0, -1.0, 1.0,   -1.0, -1.0, -1.0, 1.0,  // z = -1
        1.0, 1.0, 1.0, 1.0,   1.0, -1.0, 1.0, 1.0,   1.0, 1.0, -1.0, 1.0,   1.0, -1.0, -1.0, 1.0,    // x = 1
        1.0, 1.0, 1.0, 1.0,   1.0, 1.0, -1.0, 1.0,   -1.0, 1.0, 1.0, 1.0,    -1.0, 1.0, -1.0, 1.0,  // y = 1
        1.0, 1.0, 1.0, 1.0,   -1.0, 1.0, 1.0, 1.0,   1.0, -1.0, 1.0, 1.0,    -1.0, -1.0, 1.0, 1.0,  // z = 1
        // Put the vertices for the diamond first, then the cube vertices
        0.0, 0.85, 0.0, 1.0,  0.85, 0.0, 0.85, 1.0,   -0.85, 0.0, -0.85, 1.0,  0.0, -0.85, 0.0, 1.0,
        // Add zeroes that will be written over
        0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0
      ]);
      faaEdges = [
        [-1, -1, 0], [1, -1, 0], [-1, 1, 0], [1, 1, 0],
        [-1, 0, -1], [1, 0, -1], [-1, 0, 1], [1, 0, 1],
        [0, -1, -1], [0, 1, -1], [0, -1, 1], [0, 1, 1]
      ];
      // Ax + By + CZ + D = 0
      var faPlane = [2.0, 5.0, 3.0, 2.0];
      // Get the planar-intersection polygon
      var faPolygon = GetPlaneBoxIntersectionPolygon(faPlane, [[-1.0, 1.0],[-1.0, 1.0],[-1.0, 1.0]]);
      // Copy the polygon vertices.
      for (var i = 0; i < faPolygon.length; ++i) {
        gfaVertices[28*4 + i] = faPolygon[i];
      }
      // Each point has 4 coordinates
      giAddedPoints = faPolygon.length/4;
      var iVertices = (28 + giAddedPoints);
      gfaTransformedVertices = new Float32Array(4*iVertices);
      gfaVertexColors = new Float32Array(4*iVertices);
      CreateProgramAndContext();
      // Begin the animation loop.
      const kiIntervalId = setInterval(Render, 20);
    }

    function Render() {
      // First copy the vertices before the transformation
	  for (var i = 4*24; i < 4*24 + 16+4*giAddedPoints; ++i) {
	    gfaTransformedVertices[i] = gfaVertices[i];
      }
      // Color the first four vertices
      for (var i = 0; i < 4; ++i) {
        // Set the colors too
        gfaVertexColors[4*24 + 4*i]      = 1.0;
        gfaVertexColors[4*24 + 4*i + 1]  = 1.0;
        gfaVertexColors[4*24 + 4*i + 2]  = 1.0;
        gfaVertexColors[4*24 + 4*i + 3]  = 1.0;
      }
      for (var i = 0; i < giAddedPoints; ++i) {
        gfaVertexColors[4*28 + 4*i]      = 0.0;
        gfaVertexColors[4*28 + 4*i + 1]  = 0.0;
        gfaVertexColors[4*28 + 4*i + 2]  = 1.0;
        gfaVertexColors[4*28 + 4*i + 3]  = .75;
      }
      var faLookAtMatrix = CreateLookAtMatrix([1, 3, 2],[0, 0, 0],[0, 1, 0]);
      // Create the orthographic matrix
      var faOrthoMatrix =  CreateOrthographicMatrix(-2.0, 2.0, -2.0, 2.0, 2.0, -2.0);
      MultiplyMatrices(faOrthoMatrix, faLookAtMatrix);
      // Add the extra colors for the view cube
      for (var iFace = 0; iFace < 6; ++iFace) {
        var fBrightness = 0.0;
        if (iFace % 3 == 0) {
          fBrightness = 1.0/7.0;
        } else if (iFace % 3 == 2) {
          fBrightness = 2.0/7.0;
        } else {
          fBrightness = 4.0/7.0;
        }
        var iBase = 16*iFace;
        for (var iVertex = 0; iVertex < 4; ++iVertex) {
          var iOffset = iBase + 4*iVertex;
          gfaTransformedVertices[iOffset] = gfaVertices[iOffset];
          gfaTransformedVertices[iOffset + 1] = gfaVertices[iOffset + 1];
          gfaTransformedVertices[iOffset + 2] = gfaVertices[iOffset + 2];
          gfaTransformedVertices[iOffset + 3] = gfaVertices[iOffset + 3];
          gfaVertexColors[iOffset]     = fBrightness;
          gfaVertexColors[iOffset + 1] = fBrightness;
          gfaVertexColors[iOffset + 2] = fBrightness;
          gfaVertexColors[iOffset + 3] = .5;
        }
      }
      for (var i = 0; i < 28+4*giAddedPoints; ++i) {
        MultiplyMatrixVertex(faOrthoMatrix, gfaTransformedVertices, 4*i);
      }
      // We need to create the buffers afterward
      CreateBuffers();
      gqGL.clearColor(0.0, 0.0, 0.0, 1.0);
        gqGL.enable(gqGL.DEPTH_TEST);
        gqGL.clear(gqGL.COLOR_BUFFER_BIT | gqGL.DEPTH_BUFFER_BIT);
        // Enable alpha blending
        gqGL.enable(gqGL.BLEND);
        // Set blending function
        gqGL.blendFunc(gqGL.SRC_ALPHA, gqGL.ONE_MINUS_SRC_ALPHA);
        gqGL.drawArrays(gqGL.POINTS, 24, 4);
        // This does not work well
        //const [kiMinSize, kiMaxSize] = gqGL.getParameter(gqGL.ALIASED_LINE_WIDTH_RANGE);
        //gqGL.lineWidth(kiMaxSize);
        //gqGL.drawArrays(gqGL.LINE_LOOP, 28, giAddedPoints);
        // The six sides
        gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 0, 4);
        gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 4, 4);
        gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 8, 4);
        gqGL.drawArrays(gqGL.TRIANGLE_FAN, 28, giAddedPoints);
        gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 12, 4);
        gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 16, 4);
        gqGL.drawArrays(gqGL.TRIANGLE_STRIP, 20, 4);
    }

    // The plane is a 4d array of the form [A, B, C, D], where Ax + By + Cz + D = 0
    // The box is a 6d array of the for [[xL, xH], [yL, yH], [zL, zH]], where xL and xH
    //    are the low and high values of the x plane defining the box, and so on.
    function GetPlaneBoxIntersectionPolygon(faPlane, faaBox) {
      // The points of intersection
      var faPolyPoints = new Float32Array([0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0,
        0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0,  0.0, 0.0, 0.0, 0.0]);
      var iVertices = 0;
      // Intersect the plane with each side line of the form [xL, yL, 0] + t[0, 0, 1] = [x, y, z]
      // A(xL) + B(yL) + Ct + D = 0 => t = -(A*xL + B*yL + D)/C
      // Check whether the z-value = t is in the range [zL, zH]. If it is zL or zH, we have a triple intersection.
      // yz: 0-3; 0, zx: 4-7, xy: 8-11 : Individual edge indices are ordered ll, lh, hl, hh
      // An array of booleans used to indicate whether the intersection with the edge was already found.
      var baFound = [false, false, false,  false, false, false,  false, false, false,  false, false, false];
      // First the nonconstant dimension: the  edges where x, y, and z vary, respectively
      for (var i = 0; i < 3; ++i) {
        if (faPlane[i] != 0.0) { // If the coefficeint is zero, it is parallel and does not intersect, but may coincide
          for (var j = 0; j < 2; ++j) {
            for (var k = 0; k < 2; ++k) {
              var iEdge = 4*i + 2*j + k;
              // This check is mostly used to avoid adding corner points three times.
              if (!baFound[iEdge]) {
                // Get the intersection
                // Check its range with the ith dimension
                // If it is in range log it
                // If it is a corner, log and remove the other two edges
                // Special case: The plane contains a line. In this case, we add both corners. (e.g. x + y = 0 and x = 1 and y = -1 are sides)
                // Specail case: The plane contains a side. In this case, we add all four vertices and quit. ()
                // When x is nonconstant, we have x = -(By1 + Cz1 + D)/A, call the solution dSol
                var dSol = -faPlane[3]; // -D
                // So, loop to get the other terms
                for (var m = 1; m < 3; ++m) {
                  var iLowHigh = ((m == 1) ? j : k);
                  dSol -= faPlane[(i+m)%3]*faaBox[(i+m)%3][iLowHigh];
                }
                // Finally divide by the coefficient, since it is not zero.
                dSol /= faPlane[i];
                // Check whether the intersection is in the interval
                if (dSol >= faaBox[i][0] && dSol <= faaBox[i][1]) {
                  // Take care of the special corner cases
                  if (dSol == faaBox[(i+m)%3][0]) {
                    // This dimension is low, So, it adds nothing to the index
                    baFound[((i+1)%3)*4 + 2*k ] = true;
                    baFound[((i+2)%3)*4 + j ] = true;
                  } else if (dSol == faaBox[(k+m)%3][1]) {
                    // This dimension is high, So, it adds to the index
                    baFound[((i+1)%3)*4 + 2*k + 1 ] = true;
                    baFound[((i+2)%3)*4 + 2 + j ] = true;
                  }
                  // Add the intersection and note it
                  baFound[iEdge] = true;
                  // Finally, add the point
                  faPolyPoints[4*iVertices + i] = dSol;
                  faPolyPoints[4*iVertices + ((i+1)%3)] = faaBox[((i+1)%3)][j];
                  faPolyPoints[4*iVertices + ((i+2)%3)] = faaBox[((i+2)%3)][k];
                  faPolyPoints[4*iVertices + 3] = 1.0;
                  ++iVertices;
                }
              }
            }
          }
        } else { // In this case, the plane may align with an edge or multiple edges
          // Run though the four edges where this dimension is zero, and check whether the plane aligns with any of them
          for (var j = 0; j < 2; ++j) {
            for (var k = 0; k < 2; ++k) {
              var iEdge = 4*i + 2*j + k;
              if (!baFound[iEdge]) {
                var bIsOnPlane = (faPlane[(i+1)%3]*faaBox[(i+1)%3][j] + faPlane[(i+2)%3]*faaBox[(i+2)%3][k] + faPlane[3] == 0.0);
                if (bIsOnPlane) {
                  faPolyPoints[4*iVertices + i] = faaBox[i][0];
                  faPolyPoints[4*iVertices + ((i+1)%3)] = faaBox[((i+1)%3)][j];
                  faPolyPoints[4*iVertices + ((i+2)%3)] = faaBox[((i+2)%3)][k];
                  faPolyPoints[4*iVertices + 3] = 1.0;
                  ++iVertices;
                  faPolyPoints[4*iVertices + i] = faaBox[i][1];
                  faPolyPoints[4*iVertices + ((i+1)%3)] = faaBox[((i+1)%3)][j];
                  faPolyPoints[4*iVertices + ((i+2)%3)] = faaBox[((i+2)%3)][k];
                  faPolyPoints[4*iVertices + 3] = 1.0;
                  ++iVertices;
                  baFound[iEdge] = true;
                  // The low value. So, add nothing.
                  baFound[((i+1)%3)*4 + 2*k] = true;
                  baFound[((i+2)%3)*4 + j ] = true;
                  // The high value. So, 1 or 2.
                  baFound[((i+1)%3)*4 + 2*k + 1 ] = true;
                  baFound[((i+2)%3)*4 + 2 + j ] = true;
                }
              }
            }
          }
        }
      }
      // Now we have all of the point of the polygon. We need to order them an put them into a returned polygon array.
      // If we only have one or two points, we could return them as a special case of a point and a line segment. For, we will return null for all.
      if (iVertices == 0) {
        return null;
      } else if (iVertices == 1){
        return null;
      } else if (iVertices == 2){
        return null;
      }
      // Start with the first point and find each successive point with a common side.
      // Find the one that is closest, in case the plane coincides with a side.
      for (var i = 0; i < iVertices - 2; ++i) {
        // Use the diagonal for the initial distance.
        var fMinDistSq = (faaBox[0][0]-faaBox[0][1])*(faaBox[0][0]-faaBox[0][1]) +
          (faaBox[1][0]-faaBox[1][1])*(faaBox[1][0]-faaBox[1][1]) +
          (faaBox[2][0]-faaBox[2][1])*(faaBox[2][0]-faaBox[2][1]);
        var iClosetOnSameSide = 0;
        for (var j = i+1; j < iVertices; ++j) {
          var bOnSameSide = false;
          for (var k = 0; k < 3; ++k) {
            if ((faPolyPoints[4*i + k] == faPolyPoints[4*j + k]) &&
              (faPolyPoints[4*i + k] == faaBox[k][0] || faPolyPoints[4*i + k] == faaBox[k][1])) {
              bOnSameSide = true;
            }
          }
          if (bOnSameSide) {
            // Check whether the distance is smallest
            var fDistSq = 0.0
            for (var k = 0; k < 3; ++k) {
              var dDiff = (faPolyPoints[4*i + k]-faPolyPoints[4*j + k]);
              fDistSq += dDiff*dDiff;
            }
            if (fDistSq < fMinDistSq) {
              iClosetOnSameSide = j;
              fMinDistSq = fDistSq;
            }
          }
        }
        // Now we have the index of the closet vertex that is on the same side, make it next
        for (var j = 0; j < 3; ++j) {
          // Swap the currrent coordinate for the first point
          var dSwap = faPolyPoints[4*(i + 1) + j];
          faPolyPoints[4*(i + 1) + j] = faPolyPoints[4*iClosetOnSameSide + j];
          faPolyPoints[4*iClosetOnSameSide + j] = dSwap;
        }
      }
      // Copy the points back into an array
      // I could add in a procedure to reverse the points if they are in the wrong order. They should probably be made to be always visible.
      var faPoly = new Float32Array(4*iVertices);
      for (var i = 0; i < 4*iVertices; ++i) {
        faPoly[i] = faPolyPoints[i];
      }
      return faPoly;
    }

    // Multiply the four coordinate vertex in V at the start index
    function MultiplyMatrixVertex(faM, faV, iStart) { // V = M*V
      var faCopy = [0,0,0,0];
      for (var i = 0; i < 4; ++i) {
        faCopy[i] = faV[iStart + i];
      }
      for (iRow = 0; iRow < 4; ++iRow) {
        faV[iStart + iRow] = faM[iRow]*faCopy[0] + faM[iRow + 4]*faCopy[1] + faM[iRow + 8]*faCopy[2] + faM[iRow + 12]*faCopy[3];
      }
    }

    function Normalize(faV) {
      var fL = Math.sqrt(faV[0]*faV[0] + faV[1]*faV[1] + faV[2]*faV[2]);
      faV[0] /= fL; faV[1] /= fL; faV[2] /= fL;
    }

    function Dot(faV1, faV2) {
      return (faV1[0]*faV2[0] + faV1[1]*faV2[1] + faV1[2]*faV2[2]);
    }

    function Cross(faV1, faV2) {
      return [faV1[1]*faV2[2]-faV1[2]*faV2[1], faV1[2]*faV2[0]-faV1[0]*faV2[2], faV1[0]*faV2[1]-faV1[1]*faV2[0]];
    }

    function Difference(faV1, faV2) {
      return [faV1[0]-faV2[0], faV1[1]-faV2[1], faV1[2]-faV2[2]];
    }

    function CreateLookAtMatrix(faEye, faObject, faUp) {
      var faViewDirection = Difference(faObject, faEye);
      Normalize(faViewDirection);
      var faRight = Cross(faViewDirection, faUp);
      Normalize(faRight);
      var faStraightUp = Cross(faRight, faViewDirection);
      var faMatrix = new Float32Array([
	    faRight[0], faStraightUp[0], faViewDirection[0], 0.0,
	    faRight[1], faStraightUp[1], faViewDirection[1], 0.0,
	    faRight[2], faStraightUp[2], faViewDirection[2], 0.0,
        -Dot(faObject, faRight), -Dot(faObject, faStraightUp), -Dot(faObject, faViewDirection), 1.0]);
      return faMatrix;
    }

    function CreateOrthographicMatrix(fLeft, fRight, fBottom, fTop, fNear, fFar) {
      if (fLeft >= fRight || fBottom >=  fTop || fFar >= fNear) {
        throw 'Improper Orthographic Projection Matrix';
      }
      fDx = fRight - fLeft;
      fDy = fTop - fBottom;
      fDz = fNear - fFar;
      var faMatrix = new Float32Array([
        2.0/fDx,                 0.0,                     0.0,                   0.0,
        0.0,                     2.0/fDy,                 0.0,                   0.0,
        0.0,                     0.0,                     2.0/fDz,               0.0,
        -(fLeft + fRight)/fDx,   -(fBottom + fTop)/fDy,   -(fNear + fFar)/fDz,   1.0]);
      return faMatrix;
    }

    function MultiplyMatrices(faaM, faaA) { // M = M*A, Note M != A
      var faRow = [0,0,0,0];
      for (iRow = 0; iRow < 4; ++iRow) {
        // Copy the current row
        for(iCol = 0; iCol < 4; ++iCol) {
          faRow[iCol] = faaM[iRow + 4*iCol];
        }
        for(iCol = 0; iCol < 4; ++iCol) {
          faaM[iRow + 4*iCol] = 0.0;
          for (k = 0; k < 4; ++k) {
            faaM[iRow + 4*iCol] += faRow[k]*faaA[4*iCol + k];
          }
        }
      }
    }
    </script>
  </head>
  <body onload="Initialization()">
    <canvas id="idCanvas" width="800", height="800" style="border:1px solid blue"></canvas>
  </body>
</html>
 

Output

 
 

© 2007–2025 XoaX.net LLC. All rights reserved.